#include "db/dbenv_impl.hxx"
#include "db/metafile.hxx"
#include "dbenv.hxx"
#include "util/fs.hxx"
#include "util/lockguard.hxx"
#include "util/crc32.hxx"
#include "log/logmanager.hxx"
#include "db/datastore_impl.hxx"
#include "db/compact_impl.hxx"
#include "util/common.hxx"
#include "util/logger.hxx"
#include "log/nodatalogmanager.hxx"
#include <errno.h>

DbEnvImpl::DbEnvImpl(const std::string& home, 
                     const std::string& logpath,
                     const std::string& logmsgFile, 
                     uint32_t lgBufSize)
: m_home(home), 
  m_logdir(logpath), 
  m_logmgr(NULL),
  m_logmsgFile(logmsgFile),  
  m_lgBufSize(lgBufSize), 
  m_compact(NULL) {

}

DbEnvImpl::~DbEnvImpl() {

    if(m_compact) close();
}

DbStatus DbEnvImpl::open(uint32_t flag) {

    DbStatus ret;

    bool create = false;
	if(!FsUtility::fileExists(m_home)) {
	    create = true;
		ret = FsUtility::createDir(m_home);
        if(!ret.isok())  return ret; 
	}

    ret = setLogFile(m_logmsgFile);
    if(!ret.isok()) return ret;
    if(create) {
        Logger::log("create env home dir%s", m_home.c_str());
    }

	if(!FsUtility::isDir(m_home)) {
	
		ret = DbStatus(m_home, EINVAL);
        return ret;
	}

    if(!FsUtility::fileExists(m_logdir)) {
    
        ret = FsUtility::createDir(m_logdir);
        if(!ret.isok()) { 
            Logger::log("create env log dir%s failed, error msg:%s",
                    m_logdir.c_str(), ret.msg().c_str());
            return ret;
        } 
        Logger::log("create env log dir%s", m_logdir.c_str());
    }

    if(!FsUtility::isDir(m_logdir)) {
    
        ret = DbStatus(m_logdir, EINVAL);
        return ret;
    }

    m_flag = flag;
    if(m_flag & LOG_NO_DATA) {
        Logger::log("log flag set to LOG_NO_DATA\n");
        m_logmgr = new NodataLogManager(*this, flag);
    }
    else {
        if(m_flag & LOG_HAS_DATA) Logger::log("log flag set to LOG_HAS_DATA\n");
        else if(m_flag & LOG_SYNC) Logger::log("log flag set to LOG_SYNC\n");

        m_logmgr = new LogManager(*this, flag);
    }

    int rc = pthread_mutex_init(&m_mutex, NULL);
    if(rc != 0) return DbStatus("DbEnvImpl::init::pthread_mutex_init", rc);

    //init crc32
    CRC32::init();

    //compact
    m_compact = CompactImpl::getInstance(*this);

    //init metafile
    MetaFile::getInstance(m_home);
    MetaFile::getInstance()->init();

    Logger::log("open env succeed, log buffer size: %d", m_lgBufSize);

	return ret;
}

DbStatus DbEnvImpl::close() {

    pthread_mutex_destroy(&m_mutex);
    
    if(m_compact) { 
        m_compact->stop(true); 
        delete m_compact; 
        m_compact = NULL; 
    }

    if(m_logmgr)  { delete m_logmgr; m_logmgr = NULL;}

    Logger::log("close env succeed");

    return DbStatus(m_home, 0);
}

DbStatus DbEnvImpl::start() {
    DbStatus ret;

    ret = m_compact->init();

    ret = m_logmgr->init();
    if(!ret.isok()) return ret;
    
    Logger::log("env is ready");

    return ret;
}

DbStatus DbEnvImpl::sync() {

    DbStatus ret;

    //get sync lock
    LockGuard lock(m_mutex);

    bool compactEnabled = m_compact->isEnabled();

    //stop compact. reason1: If sync is called right before application exit, compact
    //will not be flushed, so we stop compact.
    //reason 2: make sure it is safe to delete a segment when its status is
    //not-alive.(It is possibl segment is set to not-alive right after flush
    //index array. If system crash after deleting segments and before next sync, index will be
    //dangling.)
    if(compactEnabled && !m_logmgr->inRecovery()) stopCompact(false);

    if(m_flag & (LOG_HAS_DATA | LOG_SYNC) ) {

        //the order below need to be maintained

        //get log files going to be synced
        std::set<uint32_t> logv = m_logmgr->getLogsTobesynced();

        //switch all of entry which is going to be synced
        for(std::list<DataStoreImpl*>::iterator iter = m_datastores.begin(); iter !=
                m_datastores.end(); ++iter) {

            (*iter)->switchEntries();
        }

        //flush logs
        ret = m_logmgr->flush(); if(!ret.isok()) return ret;

        //flush segment and index array
        for(std::list<DataStoreImpl*>::iterator iter = m_datastores.begin(); iter !=
                m_datastores.end(); ++iter) {

            ret = (*iter)->flush();
            if(!ret.isok()) {
                Logger::log("sync failed, error msg: %s", ret.msg().c_str());
                return ret;
            }
        }

        ret = m_logmgr->writeSyncFlag(logv);

        //it is OK that metafile is flushed after log, because log will maintain
        //the consistency
        MetaFile::getInstance()->flush(); 
    }
    else if(m_flag & LOG_NO_DATA) {
    
         //the order below need to be maintained

        //get log files going to be synced
        std::set<uint32_t> logv = m_logmgr->getLogsTobesynced();

        //switch all of entry which is going to be synced
        for(std::list<DataStoreImpl*>::iterator iter = m_datastores.begin(); iter !=
                m_datastores.end(); ++iter) {

            (*iter)->switchEntries();
        }

        //flushed segment should cover to-be-flushed log
        m_logmgr->switchLog();

        ret = flushSegments(); if(!ret.isok()) return ret;

        ret = m_logmgr->flush(); if(!ret.isok()) return ret;

        ret = flushIndexArray();  if(!ret.isok()) return ret;

        ret = m_logmgr->writeSyncFlag(logv);

        //it is OK that metafile is flushed after log, because log will maintain the 
        //consistency
        MetaFile::getInstance()->flush();
    }

    if(compactEnabled && !m_logmgr->inRecovery()) startCompact();

    Logger::log("sync done");
    
    return ret;
}

DbStatus DbEnvImpl::flushlog() {

    DbStatus ret;

    //get sync lock
    LockGuard lock(m_mutex);

    if(m_flag & (LOG_HAS_DATA | LOG_SYNC) ) {
        ret = m_logmgr->flush();
    }
    else if(m_flag & LOG_NO_DATA) {
  
        //flushed segment should cover to-be-flushed log
        m_logmgr->switchLog();

        ret = flushSegments(); if(!ret.isok()) return ret;
        ret = m_logmgr->flush(); if(!ret.isok()) return ret;
    }

    return ret;
}

//should be protected by m_mutex in nodatalogmanager
DbStatus DbEnvImpl::flushSegments() {

    DbStatus ret;

    for(std::list<DataStoreImpl*>::iterator iter = m_datastores.begin(); iter !=
            m_datastores.end(); ++iter) {

        ret = (*iter)->flushSegments();
        if(!ret.isok()) {
            Logger::log("flush segment failed, datastore: %s, error msg: %s",
                    (*iter)->getDsName().c_str(), ret.msg().c_str());
            
            return ret;
        }
    }

    return ret;
}

//should be protected by m_mutex in nodatalogmanager
DbStatus DbEnvImpl::flushIndexArray() {

    DbStatus ret;

    for(std::list<DataStoreImpl*>::iterator iter = m_datastores.begin(); iter !=
            m_datastores.end(); ++iter) {

        ret = (*iter)->flushIndexArray();
        if(!ret.isok()) {
            Logger::log("flush indexarray failed, datastore: %s, error msg: %s",
                    (*iter)->getDsName().c_str(), ret.msg().c_str());

            return ret;
        }
    }
    
    return ret;
}


std::string DbEnvImpl::getHomepath() {

	return m_home;
}


std::string DbEnvImpl::getLogpath() {

	return m_logdir;
}

void DbEnvImpl::setLogBufferSize(uint32_t size) {

	m_lgBufSize = size;
}

uint32_t DbEnvImpl::getLogBufferSize() {

	return m_lgBufSize;
}


DbStatus DbEnvImpl::setLogFile(const std::string& logFile) {

    return Logger::init(logFile); 
}

void DbEnvImpl::addStore(DataStoreImpl* store) {

   m_datastores.push_back(store); 
}


DataStoreImpl* DbEnvImpl::findStore(const std::string& storeName) {

   DataStoreImpl* ds = NULL;

   for(std::list<DataStoreImpl*>::iterator iter = m_datastores.begin(); iter !=
           m_datastores.end(); ++iter) {

       if(storeName == (*iter)->getDsName() ) return *iter; 
   }

   return ds;
}

DbStatus DbEnvImpl::compact(CompactOption& option) {

    DbStatus ret;

    for(std::list<DataStoreImpl*>::iterator iter = m_datastores.begin(); iter !=
            m_datastores.end(); ++iter) {
    
        if(*iter) ret = (*iter)->compact(option);
        if(!ret.isok()) break;
    }

    return ret;
}


void DbEnvImpl::startCompact() {

    m_compact->start();    
}


void DbEnvImpl::stopCompact(bool quit) {

    m_compact->stop(quit);
}


